Skip to content

C++14 新特性

目录

  1. 函数返回类型推导
  2. 泛型 Lambda 表达式 (Generic Lambdas)
  3. Lambda 捕获表达式 (Lambda Capture Expressions)
  4. 变量模板 (Variable Templates)
  5. [[deprecated]] 属性
  6. 二进制字面量和数字分隔符
  7. std::make_unique
  8. 编译期整数序列 (std::integer_sequence)
  9. 总结

1. 函数返回类型推导

C++11 引入了 auto 关键字用于自动推导变量类型,C++14 则更进一步,允许函数返回类型也可以自动推导。

C++11:

cpp
// C++11 需要显式指定返回类型或使用 decltype
auto add(int a, int b) -> decltype(a + b) {
    return a + b;
}

C++14:

cpp
// C++14 可以直接使用 auto 推导返回类型
auto add(int a, int b) {
    return a + b;
}

示例:

cpp
#include <iostream>

auto multiply(double x, int y) {
  return x * y;
}

int main() {
  auto result = multiply(3.14, 2);
  std::cout << "Result: " << result << std::endl; // 输出 Result: 6.28
  return 0;
}

注意事项:

  • 如果函数有多个 return 语句,它们的返回类型必须可以推导为同一类型。
  • 返回类型推导也可以用于递归函数,但至少需要一个非递归的返回语句来启动推导。
  • 不能用于虚函数,因为虚函数需要在编译时确定类型。

2. 泛型 Lambda 表达式 (Generic Lambdas)

C++11 的 Lambda 表达式已经很强大,C++14 则通过泛型 Lambda 进一步提升了其灵活性。泛型 Lambda 可以接受任意类型的参数,而无需显式指定。

C++11:

cpp
// C++11 需要为 Lambda 表达式指定参数类型
auto add = [](int a, int b) {
    return a + b;
};

C++14:

cpp
// C++14 可以使用 auto 作为参数类型,实现泛型 Lambda
auto add = [](auto a, auto b) {
    return a + b;
};

示例:

cpp
#include <iostream>
#include <string>

int main() {
  auto genericLambda = [](auto x, auto y) {
    return x + y;
  };

  std::cout << genericLambda(5, 3) << std::endl;         // 输出 8
  std::cout << genericLambda(2.5, 1.5) << std::endl;      // 输出 4
  std::cout << genericLambda(std::string("Hello, "), std::string("world!")) << std::endl; // 输出 "Hello, world!"
  return 0;
}

3. Lambda 捕获表达式 (Lambda Capture Expressions)

C++11 的 Lambda 捕获只能捕获当前作用域中已存在的变量,C++14 则允许在捕获列表中创建新的变量,并对其进行初始化。

C++11:

cpp
int x = 10;
auto lambda = [x]() { // 只能捕获已存在的变量 x
    return x * 2;
};

C++14:

cpp
auto lambda = [x = 10]() { // 可以创建并初始化新的变量 x
    return x * 2;
};

// 移动捕获
auto lambda2 = [ptr = std::make_unique<int>(5)]() {
    // 使用 ptr
};

示例:

cpp
#include <iostream>
#include <memory>

int main() {
  auto initCapture = [value = 42]() {
    return value;
  };
  std::cout << initCapture() << std::endl; // 输出 42

  auto moveCapture = [ptr = std::make_unique<int>(10)]() {
    return *ptr;
  };
  std::cout << moveCapture() << std::endl; // 输出 10
   // ptr的所有权已经被移动到lambda表达式内部,所以这里不能再使用ptr
  return 0;
}

优点:

  • 可以捕获右值引用,并在lambda内部使用.
  • 可以更灵活的修改外部变量, 而不影响外部变量本身的值.

4. 变量模板 (Variable Templates)

C++14 引入了变量模板,允许我们定义可以参数化的变量。

cpp
template<typename T>
constexpr T pi = T(3.1415926535897932385);

template<typename T>
T circularArea(T r) {
    return pi<T> * r * r;
}

示例:

cpp
#include <iostream>

template<typename T>
constexpr T pi = T(3.1415926535897932385);

int main() {
  std::cout << "Pi (float): " << pi<float> << std::endl; // 输出 Pi (float): 3.14159
  std::cout << "Pi (double): " << pi<double> << std::endl; // 输出 Pi (double): 3.141592653589793
  return 0;
}

5. [[deprecated]] 属性

C++14 引入了标准的 [[deprecated]] 属性,用于标记不推荐使用的函数、类、变量等。编译器在遇到被 [[deprecated]] 标记的实体时会发出警告。

cpp
[[deprecated("Use newFunction() instead")]]
void oldFunction() {
    // ...
}

示例:

cpp
#include <iostream>

[[deprecated("This function is deprecated. Use newFunction() instead.")]]
void oldFunction() {
  std::cout << "This is the old function." << std::endl;
}

void newFunction() {
  std::cout << "This is the new function." << std::endl;
}

int main() {
  oldFunction(); // 编译时会产生警告
  newFunction();
  return 0;
}

6. 二进制字面量和数字分隔符

  • 二进制字面量: C++14 允许使用 0b0B 前缀来表示二进制字面量。
  • 数字分隔符: C++14 允许在数字字面量中插入单引号 ' 作为分隔符,以提高可读性。
cpp
int binary = 0b10101010;
long long largeNumber = 1'000'000'000'000;
double pi = 3.141'592'653'589'793'238'462;

示例:

cpp
#include <iostream>

int main() {
  int binaryValue = 0b1101;             // 二进制表示的 13
  long long largeValue = 1'000'000'000; // 使用分隔符提高可读性

  std::cout << "Binary value: " << binaryValue << std::endl; // 输出 Binary value: 13
  std::cout << "Large value: " << largeValue << std::endl;   // 输出 Large value: 1000000000
  return 0;
}

7. std::make_unique

C++11 引入了 std::make_shared 来更安全地创建 std::shared_ptr,C++14 则补充了 std::make_unique 来创建 std::unique_ptr

cpp
#include <memory>

auto ptr = std::make_unique<int>(42);

优点:

  • 异常安全: 避免了手动 newdelete 可能导致的内存泄漏。
  • 简洁: 代码更简洁,减少了冗余。
  • 防止隐式共享: make_unique 生成的是unique_ptr, 确保独占所有权.

示例:

cpp
#include <iostream>
#include <memory>

int main() {
  // 使用 make_unique 创建 unique_ptr
  auto intPtr = std::make_unique<int>(42);
  std::cout << "Value: " << *intPtr << std::endl; // 输出 Value: 42

  // unique_ptr 具有独占所有权,不能复制
  // auto intPtr2 = intPtr; // 错误:尝试复制 unique_ptr

  // 可以通过 std::move 转移所有权
  auto intPtr2 = std::move(intPtr);

  // 此时 intPtr 已经为空,intPtr2 指向原来的内存
  if (intPtr) {
    std::cout << "intPtr is not null" << std::endl;
  } else {
    std::cout << "intPtr is null" << std::endl; // 输出 intPtr is null
  }

  if (intPtr2) {
    std::cout << "Value (intPtr2): " << *intPtr2 << std::endl; // 输出 Value (intPtr2): 42
  }

  return 0;
}

8. 编译期整数序列 (std::integer_sequence)

std::integer_sequence 是一个编译期整数序列,通常与模板元编程一起使用,可以用来生成索引序列、参数包展开等。 主要包含: std::integer_sequence: 表示一个编译时整数序列。 std::make_integer_sequence: 生成一个 std::integer_sequencestd::index_sequence: std::integer_sequence<std::size_t, ...> 的别名。 std::make_index_sequence: 生成一个 std::index_sequence

cpp
template<typename... Args, std::size_t... Is>
void printArgs(const std::tuple<Args...>& tup, std::index_sequence<Is...>) {
    (std::cout << ... << std::get<Is>(tup)) << std::endl;
}

std::tuple<int, double, std::string> tup(1, 2.5, "hello");
printArgs(tup, std::make_index_sequence<3>{}); // 依次打印 tuple 中的元素

示例

c++
#include <iostream>
#include <tuple>
#include <utility>

// 使用 std::integer_sequence 展开 tuple
template<typename Tuple, std::size_t... Is>
void printTupleHelper(const Tuple& t, std::index_sequence<Is...>) {
  ((std::cout << std::get<Is>(t) << " "), ...); //C++17 折叠表达式
  std::cout << std::endl;
}

template<typename... Args>
void printTuple(const std::tuple<Args...>& t) {
  printTupleHelper(t, std::make_index_sequence<sizeof...(Args)>{});
}

int main() {
  std::tuple<int, double, std::string> myTuple(1, 2.5, "Hello");
  printTuple(myTuple); // 输出 1 2.5 Hello
  return 0;
}

9. 总结

C++14虽然是一个相对较小的更新,但它带来的改进却非常实用,主要体现在以下几个方面:

  • 提高代码的简洁性和可读性: 函数返回类型推导、泛型Lambda、数字分隔符等特性让代码更加简洁易懂。
  • 增强泛型编程能力: 泛型Lambda、变量模板等特性使得泛型编程更加灵活强大。
  • 提升开发效率: std::make_unique、编译期整数序列等特性简化了常见任务的实现。
  • 更好的资源管理: std::make_unique, std::shared_timed_mutexstd::shared_lock 等, 提供更好的资源管理工具。
  • 更好的标记过时API: [[deprecated]] 属性。

希望通过本节课的学习,大家能够掌握C++14的新特性,并在实际开发中灵活运用,写出更高效、更优雅的代码。

10. 作业

编写一个函数 processContainer,接受一个容器(如std::vector、std::list、std::set等)和一个泛型Lambda表达式作为参数。该函数使用给定的Lambda表达式处理容器中的每个元素。

具体要求: processContainer 函数的第一个参数可以是任意类型的容器,只要该容器支持迭代器。 processContainer 函数的第二个参数是一个泛型Lambda表达式,该Lambda表达式接受一个容器元素的引用作为参数(不需要修改可以传const引用),可以对元素进行任意操作(例如,打印、修改、计算等)。 演示如何使用不同的容器和不同的Lambda表达式来调用 processContainer 函数。 至少演示三种不同的使用情况: 打印容器内容 将容器内所有元素翻倍 (对于数字) 或者 转换为大写(对于 std::string) 计算容器中所有元素的总和(对于数字)或总长度(对于 std::string)

参考实现:

c++
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>

// 你的 processContainer 函数
template<typename Container, typename Callable>
void processContainer(Container& container, Callable operation)
{
    for(auto&& element : container)  //使用 auto&&, 可以兼容左值和右值
    {
        operation(element);
    }
}

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    std::list<std::string> words = {"hello", "world", "generic", "lambda"};

   // 示例1: 打印容器内容
    processContainer(numbers, [](const auto& x){
        std::cout << x << " ";
    });
     std::cout << std::endl;
    
    processContainer(words, [](const auto& str){
        std::cout<< str << " ";
    });
    std::cout << std::endl;

     // 示例2: 将数字翻倍, 字符串转大写
    processContainer(numbers, [](auto& x) {
        x *= 2;
    });

    processContainer(words, [](auto& str)
    {
        std::transform(str.begin(), str.end(), str.begin(), ::toupper);
    });
    
      // 打印翻倍/大写后的容器
    processContainer(numbers, [](const auto& x){
        std::cout << x << " ";
    });
     std::cout << std::endl;
    
    processContainer(words, [](const auto& str){
        std::cout<< str << " ";
    });
    std::cout << std::endl;

     // 示例3: 计算数字的总和,字符串的总长度
    int sum = 0;
    processContainer(numbers, [&sum](const auto& x){
        sum += x;
    });
    
     std::cout << "Sum: " << sum << std::endl;

    size_t totalLength = 0;
    processContainer(words, [&totalLength](const auto& str){
        totalLength += str.length();
    });
      std::cout << "Total length: " << totalLength << std::endl;
    return 0;
}